####什么是梯度，在上一节中，我们见识到了有两个参数的函数，对于这种函数的求导，我们需要求两次，因为有两个参数，
####两次求导的时候，每一次，我们对于非目标参数，我们需要固定它的值，例如f（x0 ，x1） = x0方 + x1方这样的函数，当我们需要求对于（3，4）这样参数的导数时
####先求x0的导数，为此我们需要固定x1的值，即原函数将变为f(x0) = x0方 + 4方这样的函数形式，针对现在这个函数求出参数x0为3时的导数（这样的导数称为偏导数），
####按照同样的方法我们求出x1的偏导数，把我们得到的两个偏导数组合起来，称之为梯度
import numpy as np

#定义原函数
def function_1(x):
    return x[0]**2 + x[1]**2

#求解梯度的函数
#注意这里传入的x，是一组参数，例如上例中的（3，4），其中一个代表x0，一个代表x1
def numerical_gradient(f,x):
    h = 1e-4 #老规矩，定义极小值
    grad = np.zeros_like(x) #生成和x一样形状的数组，不过是里面的元素都是0，它将用来存放我们求解完成的梯度
    #下面开始进行计算,中心差分法，我们这里计算的逻辑其实依旧还是没有变，每次遍历我们都只是求某 一个 参数对应的偏导数
    for idx in range(x.size):
        tmp_val = x[idx] #这个值先存下来，后面有用
        #f(x + h)的计算
        x[idx] = tmp_val + h #中心前，自变量的值
        fxh1 = f(x) #注意看这里，我是把整套参数都扔进去计算了，但是此前，我只改变过其中的一个参数的值，这符合我们求偏导数的思想
        #f(x - h)的计算
        x[idx] = tmp_val - h #中心后，自变量的值（可怜的x[idx],在整个过程中一直被拿来做容器）
        fxh2 = f(x) #自己想
        grad[idx] = (fxh1 - fxh2)/(2*h) #每次遍历完成一个偏导数的求解，放入盛放梯度的容器中
        x[idx] = tmp_val #还原值（看到没，这个值用上了，不过这个值分明很忙一直都在用嘛）
    return grad #循环完成之时，每个参数对应的偏导数自然也就求解放好了，那么自然完成了梯度的求解

if __name__ == '__main__':
    print("----来一些具体的数值例子----")
    g1 = numerical_gradient(function_1,np.array([3.0,4.0])) #[6. 8.]
    g2 = numerical_gradient(function_1,np.array([0.0,2.0])) #[0. 4.]
    g3 = numerical_gradient(function_1,np.array([3.0,0.0])) #[6. 0.]
    print(g1)
    print(g2)
    print(g3)


